#include "repository.h"
#include "fileio/fileio.h"
#include "external/tinyxml/tinyxml2.h"
#include "debug/logger.h"
#include "G.h"
#include "misc/strutil.h"

#include <sstream>

namespace renderit
{
	namespace messiah
	{
		repository::repository()
			: fsize_(0)
		{
		}

		void repository::load_repo(const repo_loader * loader)
		{
			for (auto& repo_item : loader->items_)
			{
				fsize_ += repo_item->fsize_;

				if (item_from_guid_.find(repo_item->guid_) != item_from_guid_.end())
				{
					logger::warn("Duplicated GUID:%s", repo_item->guid_.c_str());
					continue;
				}

				item_from_guid_[repo_item->guid_] = repo_item;
				items_list_.push_back(repo_item);
				items_from_type_[repo_item->type_].push_back(repo_item);
				for (auto& dep : repo_item->get_deps())
				{
					items_from_dep_guid_[dep].insert(repo_item);
				}
				/*
				if (repo_item->repo_res_)
				{
					for (auto& g : repo_item->repo_res_->get_deps())
					{
						items_from_dep_guid_[g].insert(repo_item);
					}
				}*/				
			}

			repo_loading_count_--;
		//	logger::info("Loaded Repo(%s), Left:%d", loader->repo_name().c_str(), repo_loading_count_);
			if (repo_loading_count_ == 0)
			{
				repo_load_callback_(repo_loaders_.size());
				/*
				dump_info();

				if (G->get_argument("gen_md5") == "true")
				{
					gen_md5_works();
				}
				else
				{
					
				}*/
			}
		}

		void repository::load_repos(std::vector<std::string>& repos_path, std::function<void(int)> on_completed)
		{
			repo_load_callback_ = on_completed;

			for (auto& path : repos_path)
			{
				repo_loaders_.push_back(new repo_loader(path));
			}

			repo_loading_count_ = repo_loaders_.size();

			for (auto& repo_loader : repo_loaders_)
			{
				G->thread_worker()->dispatch([this, repo_loader]() {
					repo_loader->load_repo();
					G->thread_object()->dispatch([this, repo_loader]() {
						load_repo(repo_loader);
					});
				});
			}
		}

		void repository::load_repos(const std::string& repos_dir, std::function<void(int)> on_completed)
		{
			std::vector<std::string> repo_fnames = fileio::list_dir(repos_dir);
			std::vector<std::string> repos_path;
			for (auto& repo_fname : repo_fnames)
			{
				// ignore some repos
				if (repo_fname == "ui.local")
				{
					continue;
				}
				std::string path = fileio::join(repos_dir, repo_fname);
				repos_path.push_back(std::move(path));
			}
			return load_repos(repos_path, on_completed);			
		}

		bool repository::create_repo(const std::string& repo_path)
		{
			if (repo_loaders_.size())
			{
				logger::info("Create Repo(%s) Failed! Repository is not empty!", repo_path.c_str());
				return false;
			}

			repo_loaders_.push_back(new repo_loader(repo_path));
			return repo_loaders_[0]->create_repo();
		}

		bool repository::copy_item(repository * other, const std::string& guid)
		{
			if (!repo_loaders_.size())
			{
				logger::warn("Copy Item(%s) Failed! Repository is Empty!", guid.c_str());
				return false;
			}

			repo_item * item = other->get_item_by_guid(guid);
			if (!item)
			{
				logger::warn("Copy Item(%s) To Repo(%s) Failed! From Repo has no such item!", guid.c_str(), repo_loaders_[0]->repo_name());
				return false;
			}

			repo_loaders_[0]->copy_item(item->repo_loader_, guid);

			for (auto& it : item->get_deps())
			{
				copy_item(other, it);
			}

			return true;
		}

		void repository::dump_info()
		{
			logger::info("\nDump Repository Info --");
			logger::info("\tTotal Repo Item Count:%d, Total FSize:%llu", get_items_count(), fsize_);
			uint32 count = 0;
			for (uint32 i = 0; i < EResourceType_End; ++i)
			{
				count += items_from_type_[i].size();
				logger::info("\tType(%s) Count:%d", GResourceTypeString((EResourceType)i).c_str(), items_from_type_[i].size());
			}
			logger::info("\tTotal Count:%d", count);		
		}
		
		void repository::gen_md5s(std::function<void()> callback)
		{
			fileio::remove_file("./md5/");
			fileio::ensure_dir("./md5/");

			gen_md5_callback_ = callback;

			gen_md5_count_ = repo_loaders_.size();
			for (auto& l : repo_loaders_)
			{
				G->thread_worker()->dispatch([&, this]() {
					gen_md5_on_wt(l);
				});
			}
		}

		void repository::gen_md5_on_wt(const repo_loader * loader)
		{
		//	logger::info("Start Gen MD5 on:%s", loader->repo_name_.c_str());

			std::stringstream ss;
		
			for (auto& item : loader->items_)
			{
				const std::string& md5 = item->get_res_md5();
			//	ss << item->guid_ << "\t" << md5 << std::endl;
			}
			/*
			fileio::ifile * f = fileio::open(fileio::join("./md5", loader->repo_name_), fileio::EOpen_CreateAlways | fileio::EOpen_Write);
			std::string s = ss.str();
			f->write(s.data(), s.size());
			f->close();*/

			G->thread_object()->dispatch([this, loader]() {
				gen_md5_completed_on_ot(loader);
			});
		}

		void repository::gen_md5_completed_on_ot(const repo_loader * loader)
		{
			gen_md5_count_--;
			logger::info("Gen MD5 Completed(%s), Left:%u", loader->repo_name().c_str(), gen_md5_count_);

			if (gen_md5_count_ == 0)
			{
				if (gen_md5_callback_)
				{
					gen_md5_callback_();
				}
				return;
			}
		}

		uint32 repository::replace_guid(const std::string& from_guid, const std::string& to_guid)
		{
			if (!strutil::str_is_guid(from_guid) || !strutil::str_is_guid(to_guid))
			{
				logger::warn("repository replace guid Check Failed(%s, %s)", from_guid.c_str(), to_guid.c_str());
				return 0;
			}

			uint32 count = 0;
			auto& v = items_from_dep_guid_[from_guid];
			for (auto& item : items_from_dep_guid_[from_guid])
			{
				uint32 c = item->replace_guid(from_guid, to_guid);
				items_from_dep_guid_[to_guid].insert(item);

				count += c;
			}
			v.clear();
			return count;
		}

		void repository::save()
		{
			for (auto& loader : repo_loaders_)
			{
				loader->save();
			}
		}

		uint32 repository::remove_guid(const std::string& guid)
		{
			auto& item = item_from_guid_[guid];
			if (item)
			{
				item->remove();
			}

			item_from_guid_.erase(guid);
		//	delete item;
			return 0;
		}
	}
}